<?php
/*
 * @Author: your name
 * @Date: 2020-10-14 16:40:55
 * @LastEditTime: 2020-12-28 10:10:39
 * @LastEditors: Please set LastEditors
 * @Description: 访问日志学习
 * @FilePath: /special/application/adminapiv3/service/module_manage/netfilter/AccessLogLearnService.php
 */
namespace app\adminapiv3\service\module_manage\netfilter;

use app\adminapiv3\model\HostLearnConfigModel;
use app\adminapiv3\model\HostPortModel;
use app\adminapiv3\model\learn_data\HostLearnDataModel;
use app\common\definition\netfilter\NetfilterStudyDefinition;
use app\common\exception\CustomException;
use app\common\service\module_manage\netfilter\HostLearnData;
use app\common\service\module_manage\netfilter\HostRule;
use app\common\service\module_manage\netfilter\HostLearnConfig;
use app\common\service\HostLink;
use think\Db;
use think\Exception;

class AccessLogLearnService extends NetfilterService
{
    /**
     * @name: 配置修改
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 10:33:24
     */
    public function configModify ($host_id, $updateData)
    {
        $HostLearnConfig = new HostLearnConfig();
        $configModel = new HostLearnConfigModel();

        //查询该主机的学习配置信息
        $configInfo = $configModel->getConfigByHostId($host_id,'c.*,h.name');
        if (!$configInfo)
        {
            $this->setError($this->CODE_FAIL,'数据不存在');
            return false;
        }
        global $logArray;
        $host_name_s = $this->getHost('name', 'string');
        $logArray = array(
            'content' => '禁用防火墙日志',
            'old_info' => '主机:' . $host_name_s . $this->learnConfigLogContent($configInfo),
            'new_info' => '主机:' . $host_name_s . $this->learnConfigLogContent($updateData)
        );
        //当前最新学习批次号
        $batchNo = $configInfo['learn_number'];
        $oldBatchNo = $batchNo;
        //学习期间不允许重复学习
        if($configInfo['learn_status'])
        {
            throw new CustomException('该主机正在学习中,无法修改学习配置!', 422);
        }
        
        //立即学习
        if($updateData['now'])
        {
            //检测主机是否在线
            $HostLink = new HostLink();
            if (!$HostLink->checkOnline('netfilter')) {
                //返回当前批次号
                return $batchNo;
            }
            //判断学习模式：全新模式：批次号重置为1 累积模式 ：批次号+1
            //学习模式 1 累计学习模式 2 全新学习模式
            if($updateData['model'] == 1){
                $isNewModel = false;
                $updateData['learn_number'] = $batchNo + 1;
            }else{
                $isNewModel = true;
                $updateData['learn_number'] = 1;
            }
            $batchNo = $updateData['learn_number'];
            //学习状态: 学习中
            $updateData['learn_status'] = 1;
            //回调状态: 0.未回调
            $updateData['callback_status'] = 0;
        }
        //配置危险端口
        if($updateData['danger_port']){
            //未接管防火墙时不能配置危险端口
            if(!$configInfo['controlled_firewall']){
                $this->setError($this->CODE_FAIL,'未接管该主机防火墙,无法配置危险端口');
                //返回当前批次号
                return $batchNo;
            }
        }

        Db::startTrans();
        try{
            //生成之前批次的id缓存
            if($updateData['now'] && !$configModel->setLearnIdCache($host_id,$oldBatchNo,$isNewModel)){
                Db::rollback();
                $this->setError($this->CODE_FAIL,'图标缓存生成失败');
                return false;
            }

            $HostLearnConfig->modifyStudyConfig($host_id, $updateData);
            Db::commit();

            //返回当前批次号
            return $batchNo;
        }catch (Exception $e){
            Db::rollback();
            $this->setError($this->CODE_FAIL,'操作失败');
            return false;
        }
    }

    /**
     * @name: 设置详情
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 10:33:41
     */
    public function configInfo ($host_id)
    {
        $HostLearnConfig = new HostLearnConfig();
        $return = $HostLearnConfig->findOne($host_id);
        $nowtime = time();
        if($return['etime'] <= $nowtime)
        {
            $return['etime'] = $nowtime;
        }
        return $return;
    }

    /**
     * @name: 关闭学习
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 11:11:18
     */
    public function learnOff ($host_id)
    {
        $HostLearnConfig = new HostLearnConfig();//修改学习配置
        $HostLearnConfig->learnOff($host_id);

        global $logArray;
        $host_name_s = $this->getHost('name', 'string');
        $logArray = array(
            'content' => '结束学习',
            'new_info' => '涉及主机:' . $host_name_s
        );

    }

    /**
     * @name: 批量主机开启学习
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 11:11:34
     */
    public function bulkLearnOn($updateData)
    {
        //每个主机的批次号不一样
        $host_id_a = $this->getHost('id');
        $host_id_a = array_unique($host_id_a);

        global $logArray;
        $config_list = Db::name('host_learn_config')->whereIn('host_id', $host_id_a)->select();
        $host_name_a = $this->getHost('name');
        $old_info = '';
        foreach ($config_list as $config)
        {
            $old_info .= '主机:' . $host_name_a[$config['host_id']] . $this->learnConfigLogContent($updateData);
        }
        $logArray = array(
            'content' => '批量学习',
            'new_info' => $this->learnConfigLogContent($updateData),
            'old_info' => $old_info
        );

        $HostLearnConfig = new HostLearnConfig();
        $configModel = new  HostLearnConfigModel();

        //查询这批主机的学习配置信息
        $configListInfo = $configModel->getConfigListByHostId($host_id_a,'c.id,c.host_id,c.model,c.learn_status,c.controlled_firewall,c.learn_number,h.name');
        if(count($configListInfo) != count($host_id_a)){
            $this->setError($this->CODE_FAIL,'所选数据不存在');
            return false;
        }

        //批次号信息
        $batchs = [];
        foreach ($configListInfo as $item){
            $batchs[$item['host_id']] = [
                'host_id' => $item['host_id'], //主机id
                'batchNo' => $item['learn_number'], //当前批次号
                'oldBatchNo' => $item['learn_number'], //原批次号
                //学习模式 1 累计学习模式 2 全新学习模式
                'isNewModel' => $updateData['model'] == 1 ? false : true
            ];
        }

        //立即开始学习
        if($updateData['now'])
        {
            $unAllowedHost = [];
            //前置判断
            foreach ($configListInfo as $key => $item){
                //学习期间不允许重复学习
                if($item['learn_status']){
                    $unAllowedHost[] = '['.$item['name'].']';
                }

                //判断学习模式：全新模式：批次号重置为1 累积模式 ：批次号+1
                //学习模式 1 累计学习模式 2 全新学习模式
                if($updateData['model'] == 1){
                    //累积模式 批次号+1
                    $batchs[$item['host_id']]['batchNo']++;
                }else{
                    //全新模式 批次号 重置为1
                    $batchs[$item['host_id']]['batchNo'] = 1;
                }
            }

            if($unAllowedHost){
                $unAllowedHost = implode(',',$unAllowedHost);
                $this->setError($this->CODE_FAIL,"主机:{$unAllowedHost}正在学习中,请勿重复点击");
                return false;
            }

            //检测主机是否在线
            $HostLink = new HostLink();
            if (!$HostLink->checkOnline('netfilter')) {
                return false;
            }

            //判断学习模式：全新模式：批次号重置为1 累积模式 ：批次号+1
            //学习模式 1 累计学习模式 2 全新学习模式
            if($updateData['model'] == 1){
                $updateData['learn_number'] = Db::raw('learn_number+1'); //将原字段自加+1
            }else{
                $updateData['learn_number'] = 1; //将原字段重置为1
            }
            $updateData['learn_status'] = 1;
        }

        //危险端口配置
        if($updateData['danger_port']){
            $configModel = new HostLearnConfigModel();
            //未接管防火墙时不能配置危险端口
            $controlledFirewallList = $configModel->isControlledFirewallList($host_id_a);
            $unControlled = [];
            foreach ($controlledFirewallList as $item){
                if(!$item['controlled_firewall']){
                    $unControlled[] = $item['name'];
                }
            }
            if($unControlled){
                $unControlled = implode(',',$unControlled);
                $this->setError($this->CODE_FAIL,"主机:[{$unControlled}]未接管防火墙,无法配置危险端口");
                return false;
            }

        }

        //生成缓存
        if($updateData['now']){
            //生成之前批次的id缓存
            $result = $configModel->batchSetLearnIdCache($batchs);
            if (array_unique(array_column($result,'result')) != [true]) {
                $this->setError($this->CODE_FAIL,'图标缓存生成失败');
                return false;
            }
            //更改回调状态为0
            $updateData['callback_status'] = 0;
        }
        //保存数据
        $HostLearnConfig->modifyStudyConfig($host_id_a, $updateData);

        //返回批次信息
        return $batchs;

    }
    
    /**
     * @name: 单个主机开启学习
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 11:11:34
     */
    public function onelearnOn()
    {
        $updateData = [];
        $host_id_a = $this->getHost('id');
        $configModel = new HostLearnConfigModel();
        $hostId = current($host_id_a);
        //查询该主机的学习配置信息
        $configInfo = $configModel->getConfigByHostId($hostId,'c.id,c.host_id,c.learn_status,c.model,c.controlled_firewall,c.learn_number,c.keep_time,h.name');
        if(!$configInfo){
            $this->setError($this->CODE_FAIL,'该主机的学习配置数据不存在');
            return false;
        }

        if ($configInfo['keep_time'] == 0)
        {
            throw new CustomException('未设置学习时间!', 422);
        }

        global $logArray;
        $config = Db::name('host_learn_config')->where('host_id', $hostId)->find();
        $host_name = $this->getHost('name', 'string');

        $logArray = array(
            'content' => '开始学习',
            'new_info' => '主机:' . $host_name . '开始学习!' . $this->learnConfigLogContent($config),
        );
        
        //当前最新学习批次号
        $batchNo = $configInfo['learn_number'];
        $oldBatchNo = $batchNo;

        //学习期间不允许重复学习
        if($configInfo['learn_status']){
            $this->setError($this->CODE_FAIL,'该主机正在学习中,请勿重复点击');
            //返回当前批次号
            return $batchNo;
        }

        //检测主机是否在线
        $HostLink = new HostLink();
        if (!$HostLink->checkOnline('netfilter')) {
            //返回当前批次号
            return $batchNo;
        }
        //判断学习模式：全新模式：批次号重置为1 累积模式 ：批次号+1
        //学习模式 1 累计学习模式 2 全新学习模式
        if($configInfo['model'] == 1){
            $isNewModel = false;
            $updateData['learn_number'] = $batchNo + 1;
        }else{
            $isNewModel = true;
            $updateData['learn_number'] = 1;
        }

        $batchNo = $updateData['learn_number'];

        $HostLearnConfig = new HostLearnConfig();
        Db::startTrans();
        try{
            //生成之前批次的id缓存
            if (!$configModel->setLearnIdCache($hostId,$oldBatchNo,$isNewModel)) {
                Db::rollback();
                $this->setError($this->CODE_FAIL,'图标缓存生成失败');
                return false;
            }

            //修改防火墙配置恢复默认行为
            $HostLearnConfig->learnOn($host_id_a,$updateData);//开始学习

            Db::commit();
            //返回当前批次号
            return $batchNo;
        }catch (Exception $e){
            Db::rollback();
            $this->setError($this->CODE_FAIL, $e->getMessage());
            return false;
        }
    }

        /**
     * @name: 学习数据列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-01 16:24:15
     */
    public function LearnDataStatisticsList ($filterData)
    {
        $HostLearnData = new HostLearnData();
        $return = $HostLearnData->groupList($filterData);
        $return['data'] = $this->processStatisticsList($return['data'], $filterData['host_id']);
        return $return;
    }

    /**
     * @name: 处理统计数据
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-04 15:05:04
     */
    public function processStatisticsList ($return, $host_id)
    {
        if (empty($return))
        {
            return [];
        }
        $mark = array_column ($return, 'mark');
        $HostLearnData = new HostLearnData();
        //统计出站
        $outbount = $HostLearnData->statisticsList($host_id, $mark, 'outbount');
        //统计入站
        $inbount = $HostLearnData->statisticsList($host_id, $mark, 'inbount');
        //总访问次数
        $all_view = $HostLearnData->statisticsList($host_id, $mark, 'all_view');
        //已生成规则
        $have_rule = $HostLearnData->statisticsList($host_id, $mark, 'have_rule');

        foreach ($mark as $k => $v)
        {
            $return[$k]['out'] = isset($outbount[$v]) ? $outbount[$v] : 0 ;
            $return[$k]['in'] = isset($inbount[$v]) ? $inbount[$v] : 0 ;
            $return[$k]['have_rule'] = isset($have_rule[$v]) ? $have_rule[$v] : 0 ;
            $return[$k]['all_view'] = isset($all_view[$v]) ? $all_view[$v] : 0 ;
        }
        return $return;
    }

    /**
     * @name: 学习数据列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-01 16:24:15
     */
    public function LearnDataList ($filterData)
    {
        $HostLearnData = new HostLearnData();
        $return = $HostLearnData->list($filterData);

        return $return;
    }

    /**
     * @name: 学习数据删除
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-04 14:14:40
     */
    public function learnDataDelete($host_id, $learn_id)
    {
        $HostLearnData = new HostLearnData();
        $return = $HostLearnData->selectByLearnId($host_id, $learn_id);

        //查询子库中学习数据
        $learnDataModel = new HostLearnDataModel();
        $databaseKey = $learnDataModel->setHostId($host_id);

        global $logArray;
        $host_name = $this->getHost('name', 'string');
        $logArray = array(
            'content' => '删除学习数据',
            'new_info' => '主机:' . $host_name . '删除学习数据!学习id' . $learn_id
        );

        //分布式事务
        try {
            Db::transactionXa(function () use($learnDataModel,$learn_id ,$host_id ) {
                //删除学习数据
                $learnDataModel->whereIn('id', $learn_id)->delete();
                //删除已生成规则
                Db::name('host_rule')->where('host_id', $host_id)->whereIn('learn_id', $learn_id)->delete();

            }, [Db::connect('mysql'),Db::connect($databaseKey)]);

        } catch (Exception $e) {
            throw new CustomException('数据保存失败',201);
        }

        return $return;
    }

    /**
     * @name: 创建防火墙规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-04 16:08:00
     */
    public function createFirewallRule ($host_id, $learn_id)
    {
        $HostLearnData = new HostLearnData();
        $learnData = $HostLearnData->selectByLearnId($host_id, $learn_id);
        $HostRule = new HostRule();
        $learn_id_a = $return = [];
        foreach ($learnData as $data)
        {
            $this->processed = false;
            $clientData = $HostLearnData->clientRule($data);
            $checked = $this->processInput($clientData, $HostRule);
            if($checked)
            {
                $learn_id_a[] = $data['id'];
                $return[] = $clientData;
            }
        }
        global $logArray;
        $host_name = $this->getHost('name', 'string');
        $logArray = array(
            'content' => '学习数据转换防火墙规则',
            'new_info' => '主机:' . $host_name . '学习数据转换防火墙规则',
        );
        //子库数据库标识
        $learnDataModel = new HostLearnDataModel();
        $databaseKey = $learnDataModel->getDataBaseKey($host_id);

        //分布式事务
        try {
            Db::transactionXa(function ()use($HostLearnData,$host_id,$learn_id_a) {
                $HostLearnData->createRule($host_id, $learn_id_a);
            }, [Db::connect('mysql'),Db::connect($databaseKey)]);

        } catch (Exception $e) {
            throw new CustomException('数据保存失败:'. $e->getMessage(),201);
        }

        return $return;
    }

    /**
     * @name: 学习配置日志内容
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-12-08 15:04:50
     */
    private function learnConfigLogContent ($data)
    {
        $return = '配置[';
        //学习时间
        $return .= '学习时间:' . $data['keep_time'] . '秒';
        //学习数据规则下发策略
        $return .= '规则下发策略:' . $data['auto'] == NetfilterStudyDefinition::MANUAL_CREATE_RULE ? '手工下发规则' : '自动下发规则' ;
        //学习模式
        $return .= '学习模式:' . $data['model'] == NetfilterStudyDefinition::KEEP_LEARN ? '累计学习' : '全新学习';
        //生成规则默认处理方式
        $return .= '默认处理方式:' . $data['allow'] == NetfilterStudyDefinition::DROP_HANDLE ? '拒绝' : '通过';
        //是否立即学习
        $return .= '是否立即学习' . $data['now'] == NetfilterStudyDefinition::NOW_LEARN ? '生成配置立即学习' : '只生成配置';
        //是否记录mac地址
        $return .= '是否记录MAC地址' . $data['have_mac_addr'] == NetfilterStudyDefinition::RECORDING_MAC ? '记录主机MAC地址' : '不记录主机MAC地址';

        $return = ']';
        return $return;
    }
}